<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app"></div>
    <button onclick="change()">修改</button>
</body>
<script>
    var data = {
        test: 1,
        arr: [1, 3,7],
        obj: {
            p: 1
        }
    };

    //观察对象
    observe(data);

    function render() {
        document.querySelector("#app").innerHTML =  tag('div', {class: data.test > 1 ? 'red': ''}, [
                ...loop(data.arr, function(item, index) {
                return tag('div', {},[
                    tag('span', {},[txt(val(index))]),
                    tag('span', {},[txt('-')]),
                    tag('span', {},[txt(val(item))])
                ])
            }),
            (data.obj.p > 1) ? tag('div',{}, [txt('if语句成功')]): '',
            tag("div", {}, [txt(data.obj.p)]),
            txt(data.test)

        ]).outerHTML
    }

    function change() {
        data.test = 2;
        data.arr.push(3);
        data.obj.p = 2;
    }

    // 生成tag 节点
    function tag(tagName,attrs, children){
        let html = '';
        children.forEach(node => {
            html += node
        });
        const node = document.createElement(tagName);
        Object.keys(attrs).forEach(key => {
            node[key] = attrs[key]
        })
        children.forEach(child =>{
            if (child) {
                node.appendChild(child);
            }
        });
        return node

    }

    // 创建文字节点
    function txt(text){
        return document.createTextNode(text);
    }


    // 取值
    function val(value){
        return value
    }


    function loop(arr, callback) {
        let result = [];
        arr.forEach((v, index) => {
           result.push( callback(v, index));
        });
        return result;

    }



    function observeArray(arr){
        // 持有一个新对象，链到Array的原型链
        let arrProto = Object.create(Array.prototype);

        // 需要被重写的方法集合： 调用原数组对应方法，并加入render操作
        const methods = ['push'];
        methods.forEach(methodKey => {
            Object.defineProperty(arrProto, methodKey, {
                value: function(){
                    console.log("arr setter:", JSON.stringify(arr), methodKey);
                    //缓存原生方法，之后调用
                    const original = Array.prototype[methodKey];
                    original.apply(this,Array.from(arguments))
                    render();

                }
            })

        });

        // 把对象的原型链设置为被修改过的原型链
        Object.setPrototypeOf(arr, arrProto);
    }

    function observe(data){
        Object.keys(data).forEach(key => {
            let value = data[key];

            if (value != null && typeof value == 'object') {
                observe(value);
            }
            definePropertyGet(data,key, data[key])
        })
    }


    // 劫持对象的getter setter
    function definePropertyGet(data, key, value){
        Object.defineProperty(data, key, {
            set:function(newvalue){
              console.log("setter:", JSON.stringify(data), key, newvalue);
               value = newvalue
                render();
            },
            get: function(){
                //此处是对Array数据类型的依赖收集
                if (Array.isArray(value)) {
                    observeArray(value)
                }
                return value;
            }
        })
    }

    /**
     * 实现 tag(创建标签), loop(for循环), txt(创建文字节点), val(取值)四个方法
     * 要求点击修改按钮之后，页面发生变化
     */
</script>
</html>
